﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

namespace SqlOrm.Xml
{
    public enum Origin { DDL, SQL }
    public partial class Field
    {
        [XmlIgnore]
        public string EnumName { get; set; }
        [XmlIgnore]
        public string EnumLookupTableName { get; set; }
        [XmlIgnore]
        public string SqlDataType { get; set; }
        [XmlIgnore]
        public string SqlDeclaredType
        {
            get
            {
                if (SqlDataType == "char" || SqlDataType == "nchar")
                {
                    if (len >1)
                        return SqlDataType + "(" + len + ")";
                }
                if (SqlDataType == "varchar" || SqlDataType == "nvarchar")
                {
                    if (len == 0)
                        return SqlDataType + "(max)";
                    else
                        return SqlDataType + "(" + len + ")";
                }
                return SqlDataType;
            }
        }
        [XmlIgnore]
        public string ClrDataType { get; set; }
        [XmlIgnore]
        public string UnderlyingEnumType { get; set; }

        [XmlIgnore]
        public string SqlFieldName { get; set; }
        [XmlIgnore]
        public string ParamName { get; set; }

        public override string ToString()
        {
            return !string.IsNullOrEmpty(name) ? name : pattern;
        }

        public static void ApplyStandardFieldRules(Field field)
        {
            if (string.IsNullOrEmpty(field.title))
            {
                field.title = field.name;
                field.title = field.title.Replace(" ", "_");
                field.title = field.title.Replace("(", "_");
                field.title = field.title.Replace(")", "_");
                if (char.IsNumber(field.title[0]))
                    field.title = field.datatype.ToString()[0] + field.title;
                if (ReservedKeywords.Any(x => x == field.title))
                {
                    field.SqlFieldName = field.name;
                    field.title = "@" + field.title;
                }
            }
            if (field.key)
                field.nulls = false;

            if (string.IsNullOrEmpty(field.SqlFieldName))
                field.SqlFieldName = field.name;

            field.ParamName = field.title[0].ToString().ToLower() + field.title.Substring(1);

        }
        public static void ApplyStandardEnumFieldRules(Field field, TableDefinition table)
        {
            if (field.Enum != null && field.Enum.Count() > 0)
            {
                field.EnumName = table.ClassName + field.title;
                field.EnumLookupTableName = "lu" + field.EnumName;
            }
        }

        public static List<string> ReservedKeywords =
            new List<string> { "event", "class" };
    }
    public partial class TableDefinition
    {
        private Origin _origin = Origin.DDL;

        [XmlIgnore]
        public Origin Origin
        {
            get { return _origin; }
            set { _origin = value; }
        }

        [XmlIgnore]
        public string FullName 
        { 
            get 
            { 
                return  (!string.IsNullOrEmpty(schema) ? schema + "." : string.Empty) + name; 
            } 
        }

        [XmlIgnore]
        public string FullyQualifiedSqlName
        {
            get 
            {
                return (!string.IsNullOrEmpty(schema) ? string.Format("[{0}].", schema) : string.Empty) + string.Format("[{0}]", name);
            }
        }

        [XmlIgnore]
        public string ClassName 
        { 
            get 
            {
                StringBuilder cls = new StringBuilder();
                if (schema != null && schema != "dbo")
                    cls.Append(schema.Replace(".", ""));
                cls.Append(name.Replace(".", ""));
                cls = cls.Replace(" ", "");
                return cls.ToString();
            }
        }

        public override string ToString()
        {
            return ClassName;
        }
    }

    public enum ProcedureOutputType { Custom, Entity, DataSet, DataTable, None }

    public partial class Procedure
    {
        private Origin _origin = Origin.DDL;

        [XmlIgnore]
        public Origin Origin
        {
            get { return _origin; }
            set { _origin = value; }
        }

        [XmlIgnore]
        public string ClassName
        {
            get
            {
                StringBuilder cls = new StringBuilder();
                if (schema != null && schema != "dbo")
                    cls.Append(schema.Replace(".", ""));
                cls.Append(name.Replace(".", ""));
                cls = cls.Replace(" ", "");
                return cls.ToString();
            }
        }

        [XmlIgnore]
        public string FullyQualifiedSqlName
        {
            get
            {
                return (!string.IsNullOrEmpty(schema) ? string.Format("[{0}].", schema) : string.Empty) + string.Format("[{0}]", name);
            }
        }

        [XmlIgnore]
        public string FullName { get { return  (!string.IsNullOrEmpty(schema) ? schema + "." : string.Empty) + name;  } }

        public override string ToString()
        {
            return name;
        }

        [XmlIgnore]
        public ProcedureOutputType OutputType
        {
            get 
            {
                if (Output != null)
                {
                    if (Output.type == ProcDataOutputType.dataSet)
                        return ProcedureOutputType.DataSet;
                    if (Output.type == ProcDataOutputType.dataTable)
                        return ProcedureOutputType.DataTable;
                    if (Output.Out != null && Output.Out.Count() > 0)
                    {
                        if (Output.Out.Count() == 1 && !string.IsNullOrEmpty(Output.Out[0].basedOnEntity))
                            return ProcedureOutputType.Entity;
                        else return ProcedureOutputType.Custom;
                    }
                }
                return ProcedureOutputType.None;
            }
        }

        [XmlIgnore]
        public string MethodReturn { get; set; }

        [XmlIgnore]
        public string MethodReturnDataType { get; set; }

        [XmlIgnore]
        public List<NestedClassFields> NestedFields { get; set; }
    }

    public class NestedClassFields
    {
        public Field Field { get; set; }
        public string NestedClassName { get; set; }
        public string NestedType { get; set; }
        public override string ToString()
        {
            return string.Format("{0},{1},{2}", NestedClassName, NestedType, Field.title);
        }
    }

    public partial class DDLSchema
    {
        public TableDefinition GetTable(ForeignKey fk)
        {
            return Table.First(x => x.name == fk.table && (x.schema == fk.schema || x.schema == "dbo" && string.IsNullOrEmpty(fk.schema)));
        }
    }

    public partial class DataDictionary
    {
        public void SupressModel()
        {
            if (Definition != null)
            {
                if (Settings.Database.ver != DatabaseVersion.SqlCompact35)
                    Array.ForEach(Definition.Schema.Table.Where(x => string.IsNullOrEmpty(x.schema)).ToArray(), x => x.schema = "dbo");

                foreach (var table in Definition.Schema.Table)
                {
                    foreach (var f in table.Field)
                    {
                        Field.ApplyStandardFieldRules(f);
                        Field.ApplyStandardEnumFieldRules(f, table);
                        f.SqlDataType = DataTypeConversion.GetSQL(f);
                        f.ClrDataType = DataTypeConversion.GetCLRDataType(f);
                    }
                }
            }
            if (Definition.Procedures != null)
            {
                foreach (var proc in Definition.Procedures)
                {
                    if (proc.Input != null)
                    {
                        foreach (var f in proc.Input)
                        {
                            Field.ApplyStandardFieldRules(f);
                            f.SqlDataType = DataTypeConversion.GetSQL(f);
                            f.ClrDataType = DataTypeConversion.GetCLRDataType(f);
                        }
                    }
                    var outputFields = new List<NestedClassFields>();
                    if (proc.Output != null && proc.Output.Out != null && proc.Output.Out.Count() > 0)
                    {
                        foreach (var ouT in proc.Output.Out)
                        {
                            if (!string.IsNullOrEmpty(ouT.basedOnEntity))
                            {
                                var fields = Definition.Schema.Table.FirstOrDefault(x => x.ClassName.ToLower() == ouT.basedOnEntity.ToLower());
                                if (fields != null)
                                    outputFields.AddRange(fields.Field.Select(x=> new NestedClassFields{ Field = x, NestedClassName = ouT.nestedName, NestedType = ouT.basedOnEntity}));
                                else
                                    throw new Exception(ouT.basedOnEntity + " does not exist as an entity");
                            }
                            if (ouT.Field != null && ouT.Field.Count() > 0)
                            {
                                foreach (var f in ouT.Field)
                                {
                                    if (string.IsNullOrEmpty(f.title))
                                        f.title = f.name;
                                    if (string.IsNullOrEmpty(f.SqlFieldName))
                                        f.SqlFieldName = f.name;
                                    f.SqlDataType = DataTypeConversion.GetSQL(f);
                                    f.ClrDataType = DataTypeConversion.GetCLRDataType(f);
                                }
                                outputFields.AddRange(ouT.Field.Select(x => new NestedClassFields { Field = x, NestedClassName = ouT.nestedName, NestedType = ouT.nestedName }));
                            }
                        }
                    }
                    proc.NestedFields = outputFields;
                    if (proc.OutputType == ProcedureOutputType.Custom)
                    {
                        string retVal = "List<{0}>";
                        if (proc.Output.rows == OutputRows.single)
                            retVal = "{0}";
                        proc.MethodReturnDataType = proc.name + "Output";
                        proc.MethodReturn = string.Format(retVal, proc.MethodReturnDataType);
                    }
                    if (proc.OutputType == ProcedureOutputType.Entity)
                    {
                        string retVal = "List<{0}>";
                        if (proc.Output.rows == OutputRows.single)
                            retVal = "{0}";
                        proc.MethodReturnDataType = proc.Output.Out[0].basedOnEntity;
                        proc.MethodReturn = string.Format(retVal, proc.MethodReturnDataType);
                        // TODO: nestings don't make sense in a single entity returned environment. 
                        Array.ForEach(proc.NestedFields.ToArray(), x => x.NestedClassName = null);
                    }
                    if (proc.OutputType == ProcedureOutputType.DataSet)
                        proc.MethodReturn = "DataSet";
                    if (proc.OutputType == ProcedureOutputType.DataTable)
                        proc.MethodReturn = "DataTable";

                    #region old
                    //if (proc.Output != null && proc.Output.Field!=null)
                    //{
                    //    foreach (var f in proc.Output.Field)
                    //    {
                    //        if (string.IsNullOrEmpty(f.title))
                    //            f.title = f.name;
                    //        if (string.IsNullOrEmpty(f.SqlFieldName))
                    //            f.SqlFieldName = f.name;
                    //        f.SqlDataType = DataTypeConversion.GetSQL(f);
                    //        f.ClrDataType = DataTypeConversion.GetCLRDataType(f);
                    //    }
                    //}
                    //if (proc.Output != null)
                    //{
                    //    if (proc.Output.type == ProcOutputType.entity)
                    //    {
                    //        var fields = Definition.Schema.Table.FirstOrDefault(x => x.ClassName.ToLower() == proc.Output.basedOnEntity.ToLower());
                    //        if (fields != null)
                    //            proc.Output.Field = fields.Field;
                    //        else
                    //            throw new Exception(proc.Output.basedOnEntity + " does not exist as an entity");
                    //    }
                    //    if (proc.Output.type == ProcOutputType.custom)
                    //    {
                    //        if (!string.IsNullOrEmpty(proc.Output.basedOnEntity))
                    //        {
                    //            var outputFields = new List<Field>();
                    //            var fields = Definition.Schema.Table.FirstOrDefault(x => x.ClassName.ToLower() == proc.Output.basedOnEntity.ToLower());
                    //            if (fields != null)
                    //                outputFields.AddRange(fields.Field);
                    //            else
                    //                throw new Exception(proc.Output.basedOnEntity + " does not exist as an entity");
                    //            if (proc.Output.Field != null)
                    //                outputFields.AddRange(proc.Output.Field);
                    //            proc.Output.Field = outputFields.ToArray();
                    //        }
                    //    }
                    //}
                    #endregion
                }
            }
        }
    }
}
